package com.ctrip.cap.lanucher;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.exec.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ctrip.cap.common.shell.AvailablePortFinder;
import com.ctrip.cap.device.Device;
import com.ctrip.cap.device.android.AndroidDevice;
import com.ctrip.cap.exception.AppiumTimeoutException;

public class LinuxAppiumServer extends AbstractAppiumServer {

    private static final Logger logger = LoggerFactory
            .getLogger(MacAppiumServer.class);
    private static final long START_TIMEOUT_MILLISECONDS = 90000;

    private int bootstrapPort = -1;
    private int selendroidPort = -1;
    private int chromeDriverPort = -1;
    
    private String appiumLog;

    private Device device;
    private int pid = 0;

    private String appiumPath = "/usr/local/lib/node_modules/appium/bin/appium.js";

    /**
     * for test only
     */
    public LinuxAppiumServer(Device device) {

        if (System.getProperty("os.name").toLowerCase().contains("mac")) {
            this.device = device;
        } else {
            throw new UnsupportedOperationException(
                    "Current platform is only support to run Android case on Mac OSX abd Windows.");
        }

    }

    @Override
    public void startAppium() {
        startAppium(START_TIMEOUT_MILLISECONDS);
    }

    public void startAppium(long milliseconds) {
        List<String> cmds = buildCmds();

        final ProcessBuilder pb = new ProcessBuilder(cmds)
                .redirectErrorStream(true);

        logger.info("start appium with {}", cmds.toString());

        final Thread appium = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    process = pb.start();
                    InputStream is = process.getInputStream();
                    new StreamCleaner(is).start();

                    int exitvalue = process.waitFor();

                    logger.warn("appium server exitvalue {}", exitvalue);
                } catch (Exception e) {
                    logger.warn("startAppium", e);
                }

            }
        });
        appium.setName("Appium-" + appium.getId());
        appium.start();

        long start = System.currentTimeMillis();
        boolean state = isRunning();
        while (!state) {
            long end = System.currentTimeMillis();
            if (end - start > milliseconds) {
                this.stopAppium();
                throw new AppiumTimeoutException("Appium can't be lanuched in "
                        + milliseconds + " seconds");
            }
            state = isRunning();
        }
        logger.warn("started appium server {}", this);
    }
    
    public void restartAppium() {
        this.stopAppium();
        this.startAppium();
    }

    @Override
    public void stopAppium() {
        try {
            logger.warn("try to kill process {} Recursively", this.getPid());
            process.destroy();

        } catch (Exception e) {
            logger.warn("stopAppium", e);
        } finally {

        }
    }

    public int getPid() {
        try {
            if (pid > 0) {
                return pid;
            }
            Field field = process.getClass().getDeclaredField("pid");
            field.setAccessible(true);
            pid = (Integer) field.get(process);
            return pid;
        } catch (Exception e) {
            logger.warn("", e);
            return -1;
        }
    }

    /**
     * need to be checked
     *
     * @return
     */
    private List<String> buildCmds() {
        List<String> cmds = new LinkedList<>();
        
        appiumPort = AvailablePortFinder.getNextAvailable();
        chromeDriverPort = AvailablePortFinder.getNextAvailable();
        bootstrapPort = AvailablePortFinder.getNextAvailable();
        selendroidPort = AvailablePortFinder.getNextAvailable();
        
        cmds.add("/usr/local/bin/node");
        cmds.add(appiumPath);
        cmds.add(String.format("--port=%d", appiumPort));
        cmds.add(String.format("--chromedriver-port=%d", chromeDriverPort));
        cmds.add(String.format("--selendroid-port=%d", selendroidPort));
        cmds.add(String.format("--bootstrap-port=%d", bootstrapPort));
        if (device != null) {
            cmds.add(String.format("-U %s", device.getSerialNumber()));
            this.appiumLog = Environment.appiumLog(device.getSerialNumber());
            String qappiumLog = StringUtils.quoteArgument(this.appiumLog);
            cmds.add(String.format("--log=%s", qappiumLog));
            cmds.add("--log-timestamp");
        }
        return cmds;
    }

    public int getAppiumPort() {
        return appiumPort;
    }

    public void setAppiumPort(int appiumPort) {
        this.appiumPort = appiumPort;
    }

    @Override
    public String getAppiumLog() {
        return appiumLog;
    }

    public Device getDevice() {
        return device;
    }

    public void setDevice(AndroidDevice device) {
        this.device = device;
    }

    @Override
    public String toString() {
        return "AppiumServer [pid=" + getPid() + ",ip=" + ip + ", appiumPort="
                + appiumPort + ", appiumLog=" + appiumLog + "]";
    }

    static class StreamCleaner extends Thread {
        private InputStream inputStream;

        public StreamCleaner(InputStream inputStream) {
            super();
            this.inputStream = inputStream;
            this.setDaemon(true);
            this.setName("Appium-StreamCleaner-" + this.getId());
        }

        @Override
        public void run() {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    inputStream));
            String line;
            try {
                do {
                    line = reader.readLine();
                } while (line != null);

                reader.close();
            } catch (IOException e) {
                // logger.warn("StreamCleaner", e);
            }
        }
    }

}
